// Ajax概述
// Ajax：阿贾克斯
// 它是浏览器提供的一套方法，可以实现页面无刷新更新数据，提高用户浏览应用的体验。
// Ajax的应用场景：
// 1.页面上拉加载更多数据
// 2.列表数据无刷新分页
// 3.表单项离开焦点数据验证
// 4.搜索框提示文字下拉列表



// Ajax的运行环境
// Ajax技术需要运行在网站环境中才能生效，当前课程会使用Node创建服务器作为网站服务器。





// Ajax的实现步骤
// 1.创建Ajax对象
var ajax = new XMLHttpRequest();

// 2.告诉Ajax请求地址以及请求方式
ajax.open('get', 'http://localhost:3000');

// 3.发送请求
ajax.send();

// 4.获取服务器端给与用于端的响应数据
ajax.onload = function () {
    console.log(ajax.responseText);
}








// 使用get向服务器端提交数据，向服务器端发送数据，拼接URL地址
// ?号后面是参数，如果多个参数的话用&符号分隔
// 注意：get请求是不能提交JSON对象数据格式的，传统网站的表单提交也是不支持JSON对象格式的。
// 客户端代码
$(function () {
    var ajax = new XMLHttpRequest();
    $('.btn').click(function () {
        var params = 'name=' + $('.name').val() + '&age=' + $('.age').val();
        ajax.open('get', 'http://127.0.0.1:3000/get?' + params);
        ajax.send();
        ajax.onload = function () {
            console.log(ajax.responseText);
        }
    })
})
// 服务器端代码
app.get('/get', (req, res) => {
    res.send(req.query);
})








// 使用post方法向服务器端提交数据
// 请求报文
// 请求报文在http请求和响应的过程中传递的数据就叫报文，包括要传送的数据和一些附加信息，
// 这些数据和信息就要遵守规定好的格式

// 如果参数类型如下就是用下面的这种类型的Content-type(设置请求参数的类型)
// 例如:
// var params = 'name=' + $('.name').val() + '&age=' + $('.age').val();
ajax.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
ajax.send('name=zhangsan&age=20');



// 客户端向服务器端提交数据
// 客户端代码
$(function () {
    var ajax = new XMLHttpRequest();
    $('.btn').click(function () {
        var params = 'name=' + $('.name').val() + '&age=' + $('.age').val();
        ajax.open('post', 'http://127.0.0.1:3000/abc');
        ajax.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
        ajax.send(params);
        ajax.onload = function () {
            console.log(ajax.responseText);
        }
    })
})



// 服务器端代码

// 第三方模块(post请求的模块)
const bodyParser = require('body-parser');
// 配置post请求
app.use(bodyParser.urlencoded());
// 接收客户端提交的数据并且返回
app.post('/abc', (req, res) => {
    res.send(req.body);
    console.log(req.body);
})




// 除了使用数据名等于数据值的方式来提交数据之外，还可以使用JSON的形式来提交数据
// 请求参数的格式
// 1.applcation/x-www-form-urlencoded
// name=zhangsan&age=20&sex=male

// application/json
// {name : 'zhangsan', age : 20, sex : '男' }
// 在请求头中指定Content-Type属性的值是application/json,告诉服务器端当前请求的格式是json

// 客户端代码
$(function () {
    var ajax = new XMLHttpRequest();
    $('.btn').click(function () {
        ajax.open('post', 'http://127.0.0.1:3000/abc');
        ajax.setRequestHeader('Content-type', 'application/json');
        ajax.send(JSON.stringify({
            name: $('.name').val(),
            age: $('.age').val()
        }));
        ajax.onload = function () {
            console.log(ajax.responseText);
        }
        $('.name').val('');
        $('.age').val('');
    })
})
// 服务器端代码
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.post('/abc', (req, res) => {
    console.log(req.body);
    res.send(req.body);
})










// ajax状态码
// 在创建ajax对象，配置ajax对象，发送请求，以及接收完服务器端响应数据
// 这个过程中的每一个不走都会对应一个数值，这个数值就是ajax状态码

// 0：请求未初始化(还是没有调用open())
// 1: 请求已经建立，但是还没有发送(还没有调用send())
// 2: 请求已发送
// 3：请求正在处理中，通常响应中已经有部分数据可以用了
// 4：响应已经完成，可以获取并使用服务器响应了


ajax.readyState //获取ajax的状态码
onreadystatechange //事件：当ajax状态码发生变化时自动触发该事件



// 例如(下面的代码一定要写在send方法的前面)：
// 当ajax状态码发生变化的时候，打印它的状态码
ajax.onreadystatechange = function () {
    console.log(ajax.readyState);
    if (ajax.readyState == 4) {
        console.log(ajax.responseText);
    }
}












// ajax错误处理
// 1.网络畅通，服务器端能接收到请求，服务器端返回的结果不是预期结果。
// 可以判断服务器端返回的状态码，分别进行处理。ajax.status获取http状态码

// 2.网络畅通，服务器端没有接收到请求，返回404状态码。
// 检查请求地址是否错误

// 3.网路畅通，服务器端能够接收到请求，服务器端返回500状态码。
// 服务器端错误，找后端程序员进行沟通。

// 4.网络中断，请求无法发送到服务器端。
// 会触发ajax对象下面的onerror事件，在onerror事件处理函数中对错误进行处理。
// 例如：
ajax.onerror = function () {
    alert('网络中断无法发送ajax请求')
}





// 客户端代码
$(function () {
    var ajax = new XMLHttpRequest();
    $('.btn').click(function () {
        ajax.open('get', 'http://127.0.0.1:3000/error');
        ajax.send();
        ajax.onload = function () {
            if (ajax.status == 400) {
                alert('请求出错')
            }
            console.log(ajax.status);
            console.log(ajax.responseText);
        }
    })
})


// 服务器端代码
app.get('/error', (req, res) => {
    res.status(400).send('not ok');
})









// ajax状态码和http状态码的区别
// ajax状态码：表示ajax请求过程状态
// Http状态码：表示请求的处理结果






// http状态码
// 下面是常见的http状态码：
// 200-请求成功
// 301-资源(网页等)被永久转移到其他URL
// 404-请求的资源(网页等)不存在
// 500-内部服务器错误

// HTTP状态码分类
// 1** ：信息，服务器收到请求，需要请求者继续执行操作
// 2** ：成功，操作被成功接收并处理
// 3** ：重定向，需要进一步的操作以完成请求
// 4** ：客户端错误，请求包含语法错误或无法完成请求
// 5** ：服务器错误，服务器在处理请求的过程中发生了错误


// 100	Continue	继续。客户端应继续其请求
// 101	Switching Protocols	切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议，例如，切换到HTTP的新版本协议
// 200	OK	请求成功。一般用于GET与POST请求
// 201	Created	已创建。成功请求并创建了新的资源
// 202	Accepted	已接受。已经接受请求，但未处理完成
// 203	Non-Authoritative Information	非授权信息。请求成功。但返回的meta信息不在原始的服务器，而是一个副本
// 204	No Content	无内容。服务器成功处理，但未返回内容。在未更新网页的情况下，可确保浏览器继续显示当前文档
// 205	Reset Content	重置内容。服务器处理成功，用户终端（例如：浏览器）应重置文档视图。可通过此返回码清除浏览器的表单域
// 206	Partial Content	部分内容。服务器成功处理了部分GET请求
// 300	Multiple Choices	多种选择。请求的资源可包括多个位置，相应可返回一个资源特征与地址的列表用于用户终端（例如：浏览器）选择
// 301	Moved Permanently	永久移动。请求的资源已被永久的移动到新URI，返回信息会包括新的URI，浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
// 302	Found	临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
// 303	See Other	查看其它地址。与301类似。使用GET和POST请求查看
// 304	Not Modified	未修改。所请求的资源未修改，服务器返回此状态码时，不会返回任何资源。客户端通常会缓存访问过的资源，通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
// 305	Use Proxy	使用代理。所请求的资源必须通过代理访问
// 306	Unused	已经被废弃的HTTP状态码
// 307	Temporary Redirect	临时重定向。与302类似。使用GET请求重定向
// 400	Bad Request	客户端请求的语法错误，服务器无法理解
// 401	Unauthorized	请求要求用户的身份认证
// 402	Payment Required	保留，将来使用
// 403	Forbidden	服务器理解请求客户端的请求，但是拒绝执行此请求
// 404	Not Found	服务器无法根据客户端的请求找到资源（网页）。通过此代码，网站设计人员可设置"您所请求的资源无法找到"的个性页面
// 405	Method Not Allowed	客户端请求中的方法被禁止
// 406	Not Acceptable	服务器无法根据客户端请求的内容特性完成请求
// 407	Proxy Authentication Required	请求要求代理的身份认证，与401类似，但请求者应当使用代理进行授权
// 408	Request Time-out	服务器等待客户端发送的请求时间过长，超时
// 409	Conflict	服务器完成客户端的 PUT 请求时可能返回此代码，服务器处理请求时发生了冲突
// 410	Gone	客户端请求的资源已经不存在。410不同于404，如果资源以前有现在被永久删除了可使用410代码，网站设计人员可通过301代码指定资源的新位置
// 411	Length Required	服务器无法处理客户端发送的不带Content-Length的请求信息
// 412	Precondition Failed	客户端请求信息的先决条件错误
// 413	Request Entity Too Large	由于请求的实体过大，服务器无法处理，因此拒绝请求。为防止客户端的连续请求，服务器可能会关闭连接。如果只是服务器暂时无法处理，则会包含一个Retry-After的响应信息
// 414	Request-URI Too Large	请求的URI过长（URI通常为网址），服务器无法处理
// 415	Unsupported Media Type	服务器无法处理请求附带的媒体格式
// 416	Requested range not satisfiable	客户端请求的范围无效
// 417	Expectation Failed	服务器无法满足Expect的请求头信息
// 500	Internal Server Error	服务器内部错误，无法完成请求
// 501	Not Implemented	服务器不支持请求的功能，无法完成请求
// 502	Bad Gateway	作为网关或者代理工作的服务器尝试执行请求时，从远程服务器接收到了一个无效的响应
// 503	Service Unavailable	由于超载或系统维护，服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
// 504	Gateway Time-out	充当网关或代理的服务器，未及时从远端服务器获取请求
// 505	HTTP Version not supported	服务器不支持请求








// ajax技术在IE低版本浏览器的缓存问题
// 问题：
// 在低版本的IE浏览器中，Ajax请求有严重的缓存问题，即在请求地址不发生变化的情况下，
// 只有第一次请求会真正发送到服务器端，后续的请求都会从浏览器的缓存中获取结果。即使
// 服务器端的数据更新了，客户端依然拿到的是缓存中的旧数据。





// 同步异步概述

// 同步
// 一个人同一时间只能做一件事情，只有一件事情做完，才能做另外一件事情。
// 落实到代码上，就是异步代码虽然需要花费事件去执行，但程序不会等待异步代码执行完成后在继续
// 执行后续的代码，而是直接执行后续代码，当后续代码执行完成后再回头看异步代码是否返回结构，如
// 果已有返回结果，在调用实现准备好的回调好眼熟处理异步代码执行的结果。
console.log('before');
setTimeout(() => {
    console.log('last');
}, 2000)
console.log('after');






// ajax封装
// 问题：发送请求代码过多，发送多次请求代码冗余且重复
// 解决方案：将请求代码封装到函数中，发送请求时调用函数即可
// ajax函数封装


// ajax函数封装
function ajax(options) {
    var xhr = new XMLHttpRequest();
    xhr.open(options.type, options.url);
    xhr.send();
    xhr.onload = function () {
        options.seccess(xhr.responseText);
    }
}


// 调用函数
ajax({
    // 代表请求方式
    type: 'get',
    // 代表请求地址
    url: 'http://127.0.0.1:3000/get',
    seccess: function (data) {
        console.log('request seccess：' + data)
    },
})


// 调用函数
ajax({
    type: 'post',
    url: 'http://127.0.0.1:3000/post',
    seccess: function (data) {
        console.log("request value: " + data);
    }
})




// 请求参数要考虑的问题
// 1.请求参数位置的问题
// 将请求参数传递到ajax函数内部，在函数内部根据请求方式
// 的不同将请求参数放置在不同的位置

// get 放在请求地址的后面

// post 放在send方法中

// 2.请求参数格式问题
// application/x-www-form-urlencoded
// 参数名称=参数值&参数名称=参数值
// name=zhangsan&age=20

// application/json
// {name : 'zhangsan', age : 20}








// ajax方法的封装(最终版)
function ajax(options) {
    var defaults = {
        type: 'get',
        url: '',
        data: {},
        head: {
            'Content-type': 'application/x-www-form-urlencoded',
        },
        success: function () { },
        error: function () { }
    }
    // 对象覆盖
    Object.assign(defaults, options);


    var xhr = new XMLHttpRequest();
    // application/x-www-form-urlencoded
    var params = '';
    for (var i in defaults.data) {
        params += i + '=' + defaults.data[i] + '&';
    }
    params = params.substr(0, params.length - 1);
    if (defaults.type == 'get') {
        defaults.url = defaults.url + '?' + params;
    }

    xhr.open(defaults.type, defaults.url);

    // 判断是是get请求还是post请求
    if (defaults.type == 'post') {
        // 如果是post请求，就把提交参数放到send里面
        var ContentType = defaults.head['Content-type'];
        xhr.setRequestHeader('Content-type', ContentType);
        // 判断用户的请求方式
        if (ContentType == 'application/json') {
            xhr.send(JSON.stringify(defaults.data));
        } else {
            xhr.send(params);
        }
    } else {
        // 如果不是post请求，则send里面发送一个空
        xhr.send()
    }
    xhr.onload = function () {
        // 判断http状态码等于200的时候说明成功了
        if (xhr.status == 200) {
            // 判断响应头信息是不是JSON格式的，如果是转换成JSON字符串
            var ContentType = xhr.getResponseHeader("Content-type");
            var responseText = xhr.responseText;
            if (ContentType.includes('application/json')) {
                // 将json字符串转换成json对象
                responseText = JSON.parse(responseText)
                defaults.success(responseText, xhr);
            } else {
                defaults.success(responseText, xhr);
            }
        } else {
            defaults.error(responseText, xhr);
        }
    }
}




ajax({
    url: 'http://127.0.0.1:3000/get',
    data: {
        name: '张三',
        age: 18,
        height: '180cm',
    },
    success: function (data, xhr) {
        console.log(data);
    },
})



























// 模板引擎概述
// 作用：使用模板引擎提供的模板语法，可以将数据和HTML拼接起来
// 作用：为了使用户界面于业务数据内容分离产生的
// 官方地址：https://aui.github.io/art-template/zh-cn/index.html




// 使用步骤：
// 1.下载客户端版本
// 在浏览器中实时编译
// 下载：template-web.js（gzip: 6kb）
// 下载好了之后在html页面中引入template-web.js文件



// 2.准备art-template模板(在html页面中写)
/*
<script id='tpl' type='text/html'>
    <div class='box'></div>
</script>
*/


// 3.告诉模板引擎哪一个模板和哪一个数据进行拼接
var html = template('tpl', { username: 'zhangsan', age: 18 });

// 4.将拼接好的html字符串添加到页面中
document.getElementById('container').innerHTML = html;

// 5.通过模板语法告诉模板引擎，数据和html字符串要如何拼接
/*
<script>
    <div class='box'>{{username}}</div>
</script>
*/




// 模板引擎的在客户端的使用步骤：
/*
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="icon" href="./images/favicon.ico">
    <title>客户端模板引擎的使用</title>
    <script src="./jquery.min.js"></script>
    <script src="./template-web.js"></script>
</head>
<body>
    <div class="wrapper"></div>


    <!-- 2.准备art-template模板(只是一段代码) -->
    <script type="text/html" id='tpl'>
        <h1>{{username}}</h1>
        <h2>{{age}}</h2>
    </script>


    <!-- 3.告诉模板引擎哪一个数据和哪一个模板进行拼接 -->
    <script type="text/javascript">
        // template方法的第一个参数
        // 1.模板id  2.数据   对象类型
        // template方法的返回值就是拼接好的html字符串
        var html = template('tpl', {username : 'zhangsan', age : 18})
        document.getElementsByClassName('wrapper')[0].innerHTML = html;
    </script>
</body>
</html>
*/





// 模板引擎循环数据的方法：
{/* <script type="text/html" id="tpl">
    {{ each data }}
    <p>{{ $value }}</p>
    {{/ each}}
</script> */}









// 解决前端调用api跨域的问题(服务器端解决办法)

// 1.下载node.js第三方模块request
// npm install request

// 2.在node.js里面导入request库
const request = require('request');

// 2-2.有时候提交参数的时候不支持中文，需要进行urlencoded转码
// npm install urlencoded   //下载urlencoded转码模块
// const urlencoded = require('urlencoded')  //导入urlencoded库
// urlencoded('把需要转码的中文放到这里面')

// 3.调用request方法发送请求(这个是调用天气api的案例)(服务器端代码)
app.get('/get', function (req, res) {
    var add = req.query;   //
    for (var i in add) {
        add = i + '=' + urlencoded(add[i]);
    }
    add = 'https://binstd.apistd.com/weather/query?' + add + '&key=6hhuFLJLhn2GLtS6wyZPFnmyJ';  //把地址拼接起来
    request(add, (error, resp, body) => {  //request方法需要三个参数
        console.log(body);
        res.send(body);
    })
})

// 4.前端代码
{/* <input type="text" id="address">
<button>获取天气参数</button>
<script>
    $(function () {
        $('button').on('click', function() {
            if($('#address').val() == '') {
                alert('请输入位置');
            }else {
                var value = $('#address').val();
                click(value);
            }
            $('#address').val('')
        })

        function click(address) {
            var xhr = new XMLHttpRequest();
            xhr.open('get', 'http://127.0.0.1:3000/get?city=' + address)
            xhr.send();
            xhr.onload = function () {
                var obj = JSON.parse(xhr.responseText);
                console.log(obj.result);
            }
        }
    })
</script> */}























// 搜索框自动提示小案例
// <!DOCTYPE html>
// <html lang="en">

// <head>
//     <meta charset="UTF-8">
//     <meta http-equiv="X-UA-Compatible" content="IE=edge">
//     <meta name="viewport" content="width=device-width, initial-scale=1.0">
//     <title>客户端模板引擎</title>
//     <script src="./jquery.min.js"></script>
//     <script src="./template-web.js"></script>
//     <style>
//         * {
//             margin: 0;
//             padding: 0;
//             text-decoration: none;
//             list-style: none;
//         }


//         .wrapper {
//             width: 200px;
//             background-color: #ccc;
//             margin: 10px 0 0 10px;
//         }

//         .textBox input {
//             outline: none;
//             width: 98%;
//             height: 30px;
//         }

//         .wrapper p {
//             background-color: rgb(225, 245, 219);
//             height: 30px;
//             text-indent: 5px;
//             margin-top: 5px;
//             line-height: 30px;
//         }
//     </style>
// </head>

// <body>
//     <!-- 搜索框自动提示功能 -->
//     <div class="wrapper">
//         <div class="textBox">
//             <input type="text" placeholder="全局搜索">
//         </div>
//         <div class="pContainer">

//         </div>
//     </div>
//     <script type="text/html" id="tpl">
//         {{each data}}
//             <p>{{$value}}</p>
//         {{/each}}
//     </script>
//     <script>
//         var wrapper = document.getElementsByTagName('div')[0];
//         var textBox = wrapper.getElementsByTagName('div')[0];
//         var input = textBox.getElementsByTagName('input')[0];
//         var pContainer = wrapper.getElementsByTagName('div')[1];
//         function ajax(options) {
//             var xhr = new XMLHttpRequest();
//             xhr.open(options.type, options.url);
//             xhr.setRequestHeader('Content-Type', 'application/json')
//             xhr.send(JSON.stringify(options.data));
//             xhr.onload = function () {
//                 if (xhr.status == 200) {
//                     options.success(xhr.responseText);
//                 }
//             }
//         }
//         var time = null;
//         input.addEventListener('input', function () {
//             clearTimeout(time);
//             if (!input.value.length) {
//                 pContainer.innerHTML = '';
//             }
//             定时器的作用：防止用户输入一个字符就发送一个ajax请求
//             time = setTimeout(function () {
//                 if (input.value) {
//                     ajax({
//                         type: 'post',
//                         url: 'http://127.0.0.1:3000/post',
//                         data: {
//                             content: input.value,
//                         },
//                         success: function (data) {
//                             var html = template('tpl', { data: JSON.parse(data) });
//                             pContainer.innerHTML = html;
//                         }
//                     })
//                 }
//             }, 700)
//         })
//     </script>
// </body>

// </html>








// 省市区三级联动下拉菜单
// <!DOCTYPE html>
// <html lang="en">

// <head>
//     <meta charset="UTF-8">
//     <meta http-equiv="X-UA-Compatible" content="IE=edge">
//     <meta name="viewport" content="width=device-width, initial-scale=1.0">
//     <!-- <meta http-equiv="refresh" content="5;url=http://127.0.0.1:3000/index.html"> -->
//     <title>省市区三级联动前端代码</title>
//     <script src="./jquery.min.js"></script>
//     <script src="./template-web.js"></script>
//     <style>
//         * {
//             margin: 0;
//             padding: 0;
//             text-decoration: none;
//             list-style: none;
//         }

//         .wrapper {
//             height: 30px;
//         }
//     </style>
// </head>

// <body>
//     <!-- 省市区三级联动 -->
//     <div class="wrapper">
//         <select class="province">
//             <option value="">请选择省份</option>
//         </select>
//         <select class="city">
//             <option value="">请选择城市</option>
//         </select>
//         <select class="county">
//             <option value="">请选择县城</option>
//         </select>
//     </div>
//     <script type="text/html" id="province">
//         {{each province}}
//         <option value="{{$value.id}}">{{$value.name}}</option>
//         {{/each}}
//     </script>
//     <script type="text/html" id="city">
//         {{each city}}
//             {{each $value}}
//                 <option value="{{$value.id}}">{{$value.name}}</option>
//             {{/each}}
//         {{/each}}
//     </script>
//     <script type="text/html" id="county">
//         {{each county}}
//             {{each $value}}
//             <option>{{$value.name}}</option>
//             {{/each}}
//         {{/each}}
//     </script>
//     <script>
//         $(function () {
//             // 1.打开页面自动发送ajax请求获得省份信息
//             function ajax(options) {
//                 var xhr = new XMLHttpRequest();
//                 xhr.open(options.type, options.url);
//                 xhr.setRequestHeader('Content-Type', 'application/json')
//                 xhr.send(JSON.stringify(options.data));
//                 xhr.onload = function () {
//                     options.success(JSON.parse(xhr.responseText));
//                 }
//             }
//             ajax({
//                 type: 'post',
//                 url: 'http://127.0.0.1:3000/province',
//                 success: function (data) {
//                     var html = template('province', { province: data });
//                     $('.province').append(html);
//                 }
//             })
//             $('.province').on('change', function () {
//                 ajax({
//                     type: 'post',
//                     url: 'http://127.0.0.1:3000/city',
//                     data: {
//                         city: $(this).val(),
//                     },
//                     success: function (data) {
//                         var html = template('city', { city: data })
//                         $('.city').append(html);
//                     }
//                 })
//             })
//             $('.city').on('change', function () {
//                 ajax({
//                     type: 'post',
//                     url: 'http://127.0.0.1:3000/county',
//                     data: {
//                         county: $(this).val(),
//                     },
//                     success: function (data) {
//                         var html = template('county', { county: data });
//                         $('.county').append(html);
//                     }
//                 })
//             })
//         })
//     </script>
// </body>

// </html>


















// fromData对象的作用：
// 1.模拟html表单，相当于html表单映射成表单对象，自动将表单对象中的数据拼接成请求参数的格式。
// 2.异步上传二进制文件


// fromData对象的使用方法
// 1.准备HTML表单
/*
<form id="form">
    name : <input type="text" name="username"><br>
    password : <input type="password" name="password"><br>
    submit : <input type="button" value="提交" id="button">
</form>
*/
// 2.将HTML表单转化为formData对象,提交给客户端
var button = document.getElementById('button')
var form = document.getElementById('form');
// 封装一个ajax方法
function ajax(options) {
    var xhr = new XMLHttpRequest();
    xhr.open(options.type, options.url);
    xhr.send(options.data);
    xhr.onload = function () {
        if (xhr.status == 200) {
            options.success(JSON.parse(xhr.responseText));
        }
    }
}
// 当点击button之后提交数据
button.addEventListener('click', function () {
    // 创建一个formData对象,对象里面必须传一个参数，就是form表单的dom对象
    var formData = new FormData(form);
    ajax({
        type: 'post',
        url: 'http://127.0.0.1:3000/formData',
        data: formData,
        success: function (data) {
            console.log(data);
        }
    })
})



// 3.服务器端代码
// 以前服务器端接收post对象都是用body-parser模块来解析
// 但是body-parser模块无法处理formData对象
// 所以我们需要使用另外一个模块来出来formData对象
// 处理formData的模块名称formidable (安装命令 npm install formidable)

// 安装好了之后在app.js文件中引入formidable模块
const formidable = require('formidable');
const { serialize } = require('v8');

// 服务器端怎么接收客户端传过来的formData对象
app.post('/formData', (req, res) => {
    // 创建表单解析对象
    var form = new formidable.IncomingForm();
    // 解析客户端传递过来的formData对象
    form.parse(req, (err, fields, files) => {
        // 第一个参数err是错误对象
        // fields 里面保存了表单当中的普通请求参数
        // files 里面保存的是和文件上传相关的信息
        res.send(fields);
    })
})





// formData对象常用方法
var form = document.getElementById('form');
var formData = new FormData(form);
var userName = formData.get('username');  //获取input表单里面name为username的值
formData.set('username', 'zhangsan');     //设置表单属性(注意如果设置的属性存在则替换原有的，如果不存在则创建)
formData.delete('confirm-password');      //删除表单对象属性中的值(用途用于输入两次密码的场景，实际上只需要向服务器上传一个密码)
formData.append('username', 'zhangsan')   //向表单对象追加属性值

// 注意：set方法于append方法的区别是，在属性名已存在的情况下，set会覆盖已有的键名的值，append会保留两个值






// formData对象上传二进制文件(只能使用post方式上传,二进制文件不支持使用get上传)
// 客户端代码
/*
<form id="form">
        <input type="file" name="files" id="file">
</form>
*/
var file = document.getElementById('file');
function ajax(options) {
    var xhr = new XMLHttpRequest();
    xhr.open(options.type, options.url);
    xhr.send(options.data);
    xhr.onload = function () {
        options.success(xhr.responseText);
    }
}
// 当选择文件后执行下面的代码
file.addEventListener('change', function () {
    var formData = new FormData();
    // 上传一个文件
    formData.append('file', this.files[0])
    ajax({
        type: 'post',
        url: '/upload',
        data: formData,
        success: function (response) {
            console.log(response);
        }
    })
})




// 服务器端代码(服务器端接收文件并且设置保存路径和保留默认后缀名)
app.post('/upload', (req, res) => {
    var formData = new formidable.IncomingForm();
    // 指定客户端上传文件的存储路径(推荐写绝对路径)(如果需要写绝对路径就需要使用到path模块)
    // __dirname就是找到express文件夹
    // 这个拼接路径就是这个样子的 F:\express\public\upload
    formData.uploadDir = path.join(__dirname, 'public', 'upload');

    // 保留上传文件的后缀名(默认不会保留)
    formData.keepExtensions = true;
    formData.parse(req, (err, fileds, files) => {
        // 当上传成功之后给客户端返回一个ok
        res.send('ok');
    })
})






// formData文件上传进度展示
// 当前用户选择文件的时候
file.onchange = function () {
    xhr.upload.onprogress = function (ev) {
        // 当前文件大小/文件总大小/  在将结果转换为百分数
        // 将结果负值给进度条的宽度属性
        var wd = (ev.loaded / ev.total) * 100 + '%';
        console.log(wd);
        $('main').css('width', wd);
    }
}



// formData文件上传图片即时预览
// 在我们将图片上传到服务器端以后，服务器通常会将图片地址作为响应数据传递给客户端，
// 客户端可以从将响应数据中获取图片地址，然后将图片在显示在页面中。
// 客户端代码(懒得记笔记)
// 服务器端代码
app.post('/upload', (req, res) => {
    var form = new formidable.IncomingForm();
    form.uploadDir = path.join(__dirname, 'public', 'upload');
    form.keepExtensions = true;
    form.parse(req, (err, fileds, files) => {
        // 1.现货区文件在服务器端的完整路径
        var str = files.fileName0.path;
        // 2.把字符串进行分割
        str = str.split('public')[1];
        // 3.字符串拼接，采用域名+静态资源文件下的目录。
        str = 'http://127.0.0.1:3000' + str;
        res.send(str);
    })
})



















// 同源政策
// 什么是同源
// 如果两个页面有相同的协议、域名和端口，那么这两个页面就是同一个源，其中只要一个不相同，就是不同源。

// 同源政策的目的：是为了保证用户信息的安全，防止恶意的网站窃取数据，最初的同源政策是指A网站在客户端设置的Cookie，B网站是不能访问的。


// ajax请求限制
/* 
    ajax只能向自己的服务器发送请求。
    比如现在有一个A网站、有一个B网站，A网站中的HTML文件只能向A网站服务器发送ajax请求，
    B网站中的HTML文件只能向B网站中的Ajax请求，但是A网站是不能向B网站发送Ajax请求的，
    同理，B网站也不能向A网站发送Ajax请求。 
*/

// 跨域问题解决方案1
// 使用JSONP的方式解决同源限制问题
// jsonp设计json with padding的缩写，他不属于Ajax请求，但他可以模拟Ajax请求。
// 1、将不同源的服务器端请求地址写在script标签中的scr属性中。(script的src属性不收浏览器的同源政策影响)
//  <script src='http://127.0.0.1:3001/get'></script>


// 2、服务器端响应数据必须是一个函数调用，真正要发送给客户端的数据需要作为函数调用的参数。
const data = 'fn({name : "张三", age : 20})';
res.send(data);

// 3.在客户端全局作用域下定义函数fn
function fn(data) { }

// 4.在fn函数内部对服务器端返回的数据进行处理
function fn(data) {
    console.log(data);
}






// jsonp代码优化1 (点击发送jsonp请求)
var btn = document.getElementsByTagName('button')[0];
btn.onclick = function () {
    var script = document.createElement('script');
    script.src = 'http://127.0.0.1:3001/get'
    // 为script标签添加onload事件
    document.body.appendChild(script);
    // 监听onload事件,当script加载完成之后就删除script标签
    script.onload = function () {
        this.remove();
    }
}



// jsonp代码优化2 (客户端需要将函数名称传递到服务器端)
// 1.客户端需要将函数名称传递到服务器端(通过参数的方式将函数名称传递到服务器端，参数名和服务器端同一称为callBack)
// 客户端代码
function fn(data) {
    console.log(data);
}
window.onload = function () {
    var button = document.getElementsByTagName('button')[0];
    button.onclick = function () {
        var script = document.createElement('script');
        script.src = 'http://127.0.0.1:3001/get?callBack=abc';
        document.body.appendChild(script);
        script.onload = function () {
            document.body.removeChild(script);
        }
    }
}


// 服务器端代码：
app.get('/get', (req, res) => {
    // 接收客户端传递过来的函数的名称
    const funName = req.query.callBack;
    // 将函数名称和对应的函数调用代码返回给客户端(把函数名和需要返回给客户端的东西拼接起来)
    const result = funName + '({name : "zhangsan", age : 100})';
    res.send(result);
})







// jsonp代码优化3(封装jsonp函数，方便请求发送)
function jsonp(options) {
    var script = document.createElement('script');
    // 3.拼接字符串
    var params = '';
    for (var i in options.data) {
        params += '&' + i + '=' + options.data[i]
    }
    // 2.随机定义一个函数名
    var fnName = 'myJsonp' + Math.random().toString().replace('.', '');
    // 1.解决需要外部全局定义函数的问题
    // 它已经不是一个全局函数了
    // 我们要想办法把它变成全局函数
    window[fnName] = options.success
    // 把callBack参数在option.url这里拼接起来
    script.src = options.url + '?callBack=' + fnName + params;
    document.body.appendChild(script);
    script.onload = function () {
        this.remove();
    }
}


// 服务器端代码优化
app.get('/get', (req, res) => {
    // 方法1(视频里面看到的方法，但是自己没有实验成功)
    res.jsonp({ name: 'zhangsan', age: 18 });


    // 方法二2
    // res.jsonp() 方法干的就是下面的事情
    var funName = req.query.callBack;
    var data = JSON.stringify(req.query);
    var result = funName + '(' + data + ')';
    res.send(result);

})












// 封装一个jsonp方法
function jsonp(options) {
    var script = document.createElement('script');
    var params = '';
    for (var i in options.data) {
        params += '&' + i + '=' + options.data[i]
    }
    var funName = 'myJsonp' + Math.random().toString().replace('.', '');
    window[funName] = options.success;
    script.src = options.url + '?callback=' + funName + params;
    console.log(script.src);
    document.body.appendChild(script);
    script.onload = function () {
        this.remove();
    }
}
jsonp({
    url: 'https://binstd.apistd.com/weather/query',
    data: {
        city: '河源',
        key: '6hhuFLJLhn2GLtS6wyZPFnmyJ',
    },
    success: function (data) {
        console.log(data);
    }
});









jsonp({
    url: 'http://127.0.0.1:3001/get',
    data: {
        name: 'zhangsan',
        age: 18,
        sex: 'male',
    },
    success: function (data) {
        console.log(123);
        console.log(data);
    }
})







// 跨域问题解决方案2(在服务器端设置)
// 同源政策
// CORS跨域资源共享
// CORS：全程Cross-origin resource sharing，即跨域资源共享，它允许浏览器向跨域服务器发送Ajax请求，克服了Ajax只能同源使用的限制。

// 设置响应头信息(设置了下面两项之后就可以跨域访问了)
// 1.允许哪些客户端访问我
// *号代表允许所有的客户端访问我
res.header('Access-Control-Allow-Origin', '*')

// 2.允许客户端使用哪些请求方法访问我
// 单独写get表示只能用get请求访问我
// 如果写get,post表示能使用get和post请求访问我
res.header('Access-Control-Allow-Methods', 'get,post')

// 服务器端代码：
app.get('get', (req, res) => {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Methods', 'get,post');
    res.send({ name: 'zhangsan', age: 18 })
})



// 全局设置CORS解决方案(就不用每一条路由都设置)
// 使用express的中间件，先拦截所有的请求，然后在对所有的请求设置上面两个响应头
// 在所有路上的上面写上下面这段代码
app.use((req, res, next) => {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Methods', 'get,post');
    next();
})















// 跨域问题解决方案三：(访问非同源数据，服务器端解决方案)
// 同源政策是浏览器给予ajax的限制，服务器端是不存在同源限制的。
app.get('/get', (req, res) => {
    var address = 'http://127.0.0.1:3001/get';
    request(address, (err, resp, body) => {
        // address ：地址信息
        // err：错误对象
        // resp ： 服务器端的一些响应信息
        // body ：服务器端响应内容
        res.send(body);
    })
})







// 跨域请求中携带cookie的问题
// http中的一项规则：客户端和服务器端的沟通是无状态性的
// withCredentials属性
// 在ajax发送跨域请求时；默认情况下不会在请求中携带cookie信息
// withCredentials：指定在涉及跨域请求时，是否携带cookie信息，默认值为false
// Access-Control-Allow-Credentials: true允许客户端发送请求时携带cookie

// 客户端代码
// 当发送跨域请求时携带cookie信息
xhr.withCredentials = true;

// 服务器端代码
res.header('Access-Control-Allow-Credentials', true);









// 使用jquery封装的ajax请求
$.ajax({
    // 设置发送请求方式的类型
    type: 'get',
    // 设置发送给地址
    url: 'http://127.0.0.1:3000/get',
    // 指定参数
    data: {
        name: 'zhangsan',
        age: 18,
    },
    // 指定类型(默认类型是application/x-www-form-urlencoded)
    // 如果要传递json类型的数据就需要把contentType的值改为application/json
    // 然后在data参数中传递一个JSON.stringify({name : zhangsan, age : 18})
    contentType: 'application/x--www-form-urlencoded',
    // 在ajax发送发型前执行的函数
    beforeSend: function () {
        return false;  //如果返回false则取消执行后面的方法
    },
    success: function (response) {
        console.log(response)
    },
    error: function (xhr) {
        console.log(xhr)
    }
})


// jquery提供的form表单字符串拼接方法
var params = $('form').serialize();
var params = $('.form').serializeArray();

// 封装一个获取form表单并且转换成对象的方法
function serializeArray(obj) {
    obj = $(obj).serializeArray();
    var params = {};
    $.each(obj, (index, value) => {
        params[value.name] = value.value;
    })
    return params;
}







// 使用jquery发送jsonp请求代码
$(function () {
    $.ajax({
        dataType: 'JSONP',
        // 修改callback参数名称
        jsonp: 'cb', //例如：http://127.0.0.1:3000/get?cb=(就是把原来的callback该成cb)
        // 指定函数名称
        jsonCallback: 'fnName',  //例如：http://127.0.0.1:3000/get?cb=fnName(如果你不想使用ajax请求成功的函数access就用这个参数改，需要提前在全局作用域下定义这个函数)
        type: 'get',
        url: 'https://binstd.apistd.com/weather/query?city=北京&key=6hhuFLJLhn2GLtS6wyZPFnmyJ',
        success: function (data) {
            console.log(data);
        }
    })
})










// $.get和$.post方法，两者的使用方法一样
// 第一个值是请求地址(字符串)
// 第二个值是请求参数(对象或字符串， 可选参数)
// 第三个值是回调函数实际上就是access函数

$.post('http://127.0.0.1:3000/get', { name, zhangsan, age: 18 }, function (response) {
    console.log(response);
})
$.get('/get', 'name=zhangsan&age=18', (response) => {
    console.log(response);
})









// jquery中的ajax全局事件
// 只要页面中有ajax请求被发送，对应的全局事件就会被触发

// ajaxStart：ajax请求开始前
// ajaxSend：ajax请求时
// ajaxSuccess：ajax获取数据后
// ajaxError：ajax请求发生错误后
// ajaxStop：ajax请求停止后
// ajaxComplete：ajax请求完成时

// 例如：
$(document).on('ajaxStart', function () {
    alert('test');
})








// 进度条
// nprogress
// 官宣：纳米级进度条，使用逼真的流动画来告诉用户正在进行的事情！
// 在html页面中引入nprogress的css以及js(下载地址：https://ricostacruz.com/nprogress/)
// 调用开始开始的方法和结束的方法
NProgross.start();  //进度条开始
NProgress.done();   //进度条结束















// RESTful风格api
// 使用不同的请求方式代表不同的操作
// GET 用来获取资源
// POST 用来新建资源（也可以用于更新资源）
// PUT 用来更新资源
// DELETE 用来删除资源
// 比如：
// DELETE http://api.qc.com/v1/friends: 删除某人的好友 （在http parameter指定好友id）
// POST http://api.qc.com/v1/friends: 添加好友
// UPDATE http://api.qc.com/v1/profile: 更新个人资料


// 案例：
// 客户端实现方法
$.ajax({
    type: 'put',
    url: 'http://127.0.0.1:3000/put',
    data: {
        name: 'zhangsan',
        age: 18,
    },
    success: function (response) {
        console.log(response);
    }
})
$.ajax({
    type: 'delete',
    url: 'http://127.0.0.1:3000/delete',
    data: {
        name: 'zhangsan',
        age: 18,
    },
    success: function (response) {
        console.log(response);
    }
})



// 服务器端代码
app.put('/put', (req, res) => {
    res.send(req.body);
})

app.delete('/delete', (req, res) => {
    res.send(req.body);
})
















// xml基础
// 如何处理服务器端传输过来的xml文档，获取里面的值
// 例如：
// 客户端代码
var xhr = new XMLHttpRequest();
xhr.open('get', 'http://127.0.0.1:3000/get');
xhr.send();
xhr.onload = function () {
    var xmlDocument = xhr.responseXML;
    var title = xmlDocument.getElementsByTagName('title')[0];
    // 获取title标签里面的内容
    console.log(title.innerHTML);
}



// 服务器端代码
app.get('/get', (req, res) => {
    // 设置请求头的类型为text/html（必须设置否则客户端无法通过responseXML获取xml文档信息）
    res.header('content-type', 'text/xml')
    res.send('<content><title>xml基础</title><text>xml文档的处理</text></content>')
})
